"""

Server2Client:
1. 系统公告  "0 0"
    1.1 服务器关机公告
    1.2 用户加入公告
    1.3 用户退出公告
2. 系统提示  "0 x"
    2.1 服务器关机通知 0 2
    2.2 昵称已占用  0 2
    2.3 私聊不存在  0 1
3. 公共消息  "sb 0"
4. 私聊消息  "sb 1"

"""

import concurrent.futures as cf
import sqlite3
import sys
from datetime import datetime
from queue import Queue
from socket import *


def info_split(string):
    eles = string.split(" ")
    return eles[0], eles[1], " ".join(eles[2:])


def init_record(database_name, c_table_name, r_table_name):
    conn = sqlite3.connect(database_name)
    cursor = conn.cursor()

    cursor.execute(f"select count(*) from sqlite_master where type = 'table' and name = '{c_table_name}'")
    exist = cursor.fetchone()[0]
    if not exist:
        c_table_create = \
            f"""
                    CREATE TABLE {c_table_name} (
                        ip VARCHAR(15) NOT NULL,
                        port SMALLINT NOT NULL,
                        name VARCHAR(16) NOT NULL,
                        log VARCHAR(3) NOT NULL,
                        logtime CHARACTER(26) NOT NULL,
                        PRIMARY KEY(ip, port, log, logtime))
                    """
        cursor.execute(c_table_create)

    # 检查缓存表是否存在，不存在则创建
    cursor.execute(f"select count(*) from sqlite_master where type = 'table' and name = '{r_table_name}'")
    exist = cursor.fetchone()[0]
    if not exist:
        r_table_create = \
            f"""
            CREATE TABLE {r_table_name} (
                kind SMALLINT NOT NULL,
                from_sb VARCHAR(16) NOT NULL,
                to_sb VARCHAR(16) NOT NULL,
                private CHARACTER(1) NOT NULL,
                datetime CHARACTER(26) NOT NULL,
                msg VARCHAR NOT NULL,
                PRIMARY KEY(kind, from_sb, to_sb, datetime))
            """
        cursor.execute(r_table_create)
        conn.commit()
    cursor.close()
    conn.close()


class LogoutException(Exception):
    pass


class Manager:

    def __init__(self, host, port, max_conn=10):
        print("欢迎使用<YJ>的聊天室")
        self._host = host
        self._port = port
        self._max_conn = max_conn

        self._server = socket(AF_INET, SOCK_STREAM)
        self._addr_list = {}
        self._n2a = {}

        self._db_info = ("server_record.db", "client_info", "server_record")
        init_record(*self._db_info)

        print("信息载入成功！")
        print(f"Server IP & port is: {self._host}:{self._port}")

    def server(self):
        self._server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        self._server.bind((self._host, self._port))
        print("地址端口绑定成功！")
        self.main()

    def main(self):
        self._server.listen(self._max_conn)
        print("正在监听中...")
        with cf.ThreadPoolExecutor() as t_exec:
            while True:
                try:
                    conn, addr = self._server.accept()
                    print(f"{addr}尝试连接")
                    from_sb = conn.recv(RSIZE).decode('utf-8')[31:]  # 获取用户姓名
                    if self._n2a.get(from_sb, 0):
                        conn.send(f"0 2 {datetime.now()} 昵称被占用".encode("utf-8"))
                        conn.close()
                        print(f"{addr}连接失败，原因：昵称被占用")
                    else:
                        futures = []
                        self._n2a[from_sb] = addr
                        t = datetime.now()
                        self.save_clinfo(from_sb, "in", t)
                        self._addr_list[addr] = (Queue(10), from_sb)
                        futures.append(t_exec.submit(self.login, conn, from_sb))
                        futures.append(t_exec.submit(self.send_to, conn, addr))
                        print(f"{addr}连接成功！")
                except:
                    break
            self._server.close()

    def login(self, conn, from_sb):
        sql_conn = sqlite3.connect(self._db_info[0])
        t = datetime.now()
        self.send_all(f"{t} [系统公告] <{from_sb}>加入了聊天室.", "0", "0")
        while True:
            try:
                self.receive(conn, sql_conn, from_sb)
            except LogoutException:
                break
        conn.close()

    def logout(self, conn, from_sb):
        t = datetime.now()
        self.send_all(f"{t} [系统公告] <{from_sb}>退出了聊天室", "0", "0")  # 1.3 用户退出公告
        self.send_to_q(self._addr_list[self._n2a[from_sb]], "")
        self.save_clinfo(from_sb, "out", t)
        addr = self._n2a.pop(from_sb)
        self._addr_list.pop(addr)
        conn.close()
        raise LogoutException

    def receive(self, conn, sql_conn, from_sb):
        data = conn.recv(RSIZE)
        if not data:
            raise LogoutException
        string = data.decode("utf-8")
        print(f"recv {from_sb}: " + string)
        # 拆分信息
        to_sb, private, tmsg = info_split(string)
        t = datetime.now()
        self.save_record(sql_conn, 0, from_sb, to_sb, private, t, tmsg)
        if private == "0":
            # 公发消息
            self.send_all(tmsg, from_sb, private)
        elif to_sb == "0":
            # 退出消息
            self.logout(conn, from_sb)
        else:
            # 私聊消息
            y = self._n2a.get(to_sb, None)
            if y is None:
                # 2.2 用户不存在
                self.send_to_q(self._addr_list[self._n2a[from_sb]], tmsg[0:26] + " 用户不存在!", private="1")
            else:
                self.send_to_q(self._addr_list[y], tmsg, from_sb, "1")
                self.send_to_q(self._addr_list[self._n2a[from_sb]], tmsg[0:26] + " 私聊消息发送成功！", private="1")

    def send_to_q(self, q, tmsg, from_sb="0", private="0"):
        """将消息压入对应发送队列

        :param q: 发送队列
        :param tmsg: 带时间的消息字符串
        :param from_sb: 消息来源， “00”是系统
        :param private: 是否为私聊，“0”为否
        :return:
        """
        q[0].put((from_sb, private, tmsg))

    def send_all(self, tmsg, from_sb, private):
        if len(self._n2a):
            # 3 公共消息
            for addr in self._n2a.values():
                self.send_to_q(self._addr_list[addr], tmsg, from_sb, private)

    def send_to(self, conn, addr):
        """从队列获取信息，发送给客户端

        :param conn: 连接
        :param addr: 地址信息
        :return:
        """
        sql_conn = sqlite3.connect(self._db_info[0])
        i_tuple = self._addr_list[addr]
        q = i_tuple[0]
        while True:
            try:
                s_tuple = q.get()
                string = " ".join(s_tuple)
                print("send: " + string)
                if string == f"0 0 ":
                    break
                else:
                    conn.send(string.encode('utf-8'))
                    t = datetime.now()
                    self.save_record(sql_conn, 1, s_tuple[0], i_tuple[1], s_tuple[1], t, s_tuple[2][27:])
            finally:
                pass
        sql_conn.close()

    def save_record(self, conn, kind, from_sb, to_sb, private, t, msg):
        """

        :param conn:
        :param kind:
        :param from_sb:
        :param to_sb:
        :param private:
        :param t:
        :param msg:
        :return:
        """
        conn.execute(
            f"INSERT INTO {self._db_info[2]} VALUES ({kind}, '{from_sb}', '{to_sb}', '{private}', '{t}', '{msg}')")
        conn.commit()

    def save_clinfo(self, usr_name, log, log_time):
        """

        :param usr_name:
        :param log:
        :param log_time:
        :return:
        """
        conn = sqlite3.connect(self._db_info[0])
        addr = self._n2a[usr_name]
        conn.execute(
            f"INSERT INTO {self._db_info[1]} VALUES ('{addr[0]}', {int(addr[1])}, '{usr_name}', '{log}', '{log_time}')")
        conn.commit()
        conn.close()


if __name__ == '__main__':
    HOST = sys.argv[1]  # "39.110.119.252"
    PORT = int(sys.argv[2])
    RSIZE = 1024
    m = Manager(HOST, PORT)
    m.server()
